package org.orbis.memorycontroller;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Map.Entry;
import java.util.Set;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.msgpack.MessagePackObject;
import org.msgpack.object.ArrayType;
import org.msgpack.object.IntegerType;
import org.msgpack.object.NilType;
import org.msgpack.object.RawType;
import org.orbis.constants.Constants;
import org.orbis.logging.LogParser;
import org.orbis.memoryblock.MemoryBlock;
import org.orbis.memoryblock.appendix.AppendixBlock;
import org.orbis.memoryblock.content.ContentBlock;
import org.orbis.memoryblock.dictionary.DictionaryBlock;
import org.orbis.memoryblock.invert.InvertIndexBlock;
import org.orbis.memoryblock.keyvalue.KeyValueBlock;
import org.orbis.memoryblock.skippointer.ContentSkipPointerBlock;
import org.orbis.memoryblock.skippointer.SkipPointerBlock;
import org.orbis.memorycontroller.struct.ClusterTuple;
import org.orbis.memorycontroller.struct.FileBufferTuple;
import org.orbis.memorycontroller.struct.MemoryInfoMaterial;
import org.orbis.structure.ContentInfo;
import org.orbis.structure.LookupPointer;

/** This class aims to manage for allocating memory-block and swap memory-block
 * to disk enough surviving with limited memory.
 * 
 * @author Yusuke Fujisaka */
public class MemoryAllocator{
	private static final int MEM_TYPES = Constants.MEMORY_TYPE_KINDS;
	private static final int MEM_DICT = Constants.MEMORY_TYPE_DICT;
	private static final int MEM_INVINDEX = Constants.MEMORY_TYPE_INVINDEX;
	private static final int MEM_SKIPPTR = Constants.MEMORY_TYPE_SKIPPTR;
	private static final int MEM_CONT = Constants.MEMORY_TYPE_CONTENTS;
	private static final int MEM_SKIPCONT = Constants.MEMORY_TYPE_SKIPPTRCONT;
	private static final int MEM_APX = Constants.MEMORY_TYPE_APPENDIX;
	private static final int MEM_KEYVAL = Constants.MEMORY_TYPE_KEYVALUE;
	private MemoryBlock[] blocks;
	private int allocated;
	private DiskController disks;

	private LookupPointer[] firstPoint;
	private LookupPointer[] lastPoint;
	private LookupPointer[] contentFirstPoint;
	private LookupPointer[] contentLastPoint;
	private LookupPointer[] contentSkipFirstPoint;
	private LookupPointer[] contentSkipLastPoint;
	private LookupPointer[] appendixFirstPoint;
	private LookupPointer[] appendixLastPoint;

	private int replicate;
	private int contentNum, appendixNum, boolFlagNum, intFlagNum, longFlagNum, floatFlagNum;
	private int blockSize;
	private int blockNum;
	private int uniqueKeyIndex;
	private int[] assignedIDs;
	private int[] clusterIDs;

	public MemoryAllocator(ContentInfo contentInfo, int replicate, int blockSize, int uniqueKeyIndex,
		DiskController disk){
		this.disks = disk;
		this.replicate = replicate;
		this.contentNum = contentInfo.contentsNum();
		this.appendixNum = contentInfo.appendixNum();
		this.boolFlagNum = contentInfo.binaryFlagNum();
		this.intFlagNum = contentInfo.integerFlagNum();
		this.longFlagNum = contentInfo.longFlagNum();
		this.floatFlagNum = contentInfo.floatFlagNum();
		this.blockSize = blockSize;
		this.uniqueKeyIndex = uniqueKeyIndex;
		// Temporary value
		File rootFile = new File("/");
		this.blockNum = (int)(rootFile.getFreeSpace() / blockSize);
		this.assignedIDs = new int[replicate];
		this.clusterIDs = new int[replicate];

		initBlockInfo();
		readAll();
	}

	private int vacantBlock(){
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] == null) return i;
		}
		return -1;
	}

	public void initCluster(int myOrder, int totalNode){
		// System.out.println("[DEBUG] Order: " + myOrder + " Total node: " + totalNode);
		if(totalNode <= replicate){
			for(int i = 0; i < totalNode; i++)
				clusterIDs[i] = i;
			for(int i = totalNode; i < replicate; i++)
				clusterIDs[i] = -1;
		}else{
			int assign;
			for(int i = 0; i < replicate; i++){
				assign = myOrder - replicate + i + 1;
				if(assign < 0) assign += totalNode;
				clusterIDs[i] = assign;
			}
		}

		if(allocated == 0){
			// Initial allocate
			for(int i = 0; i < replicate; i++)
				initializeAllocate(clusterIDs[i]);
		}
	}

	private void resetCluster(int pastCluster, int newCluster){
		for(int i = 0; i < blockNum; i++){
			if(blocks[i] == null) continue;
			if(blocks[i].getCluster() == pastCluster){
				int blockID = blocks[i].getID();
				boolean renumbered = disks.renumberID(pastCluster, blockID, newCluster, blockID);
				if(renumbered) blocks[i].setCluster(newCluster);
			}
		}
	}

	// TODO: This procedure do not adapt to dynamic node-adding
	public void renumberCluster(int myOrder, int totalNode){
		int assign;

		// System.out.println("[DEBUG] Order: " + myOrder);
		for(int i = 0; i < replicate; i++){
			if(i >= totalNode) return;
			// assign = myOrder - replicate + i + 1;
			assign = myOrder - replicate + i + 1;
			if(assign < 0) assign += totalNode;
			// System.out.printf("[DEBUG] Iteration-%d ClusterID-%d assign-%d%n", i, clusterIDs[i], assign);
			if(clusterIDs[i] == -1){
				// System.out.println("[DEBUG] Allocate");
				clusterIDs[i] = assign;
				initializeAllocate(assign);
			}else if(clusterIDs[i] != assign){
				System.out.printf("[DEBUG] Relocate: %d -> %d%n", clusterIDs[i], assign);
				resetCluster(clusterIDs[i], assign);
				clusterIDs[i] = assign;
			}
		}

		disks.resetRenumbered();
	}

	private int clusterIndex(int ID){
		for(int i = 0; i < clusterIDs.length; i++)
			if(clusterIDs[i] == ID) return i;

		return -1;
	}

	public int clusterID(int index){
		if(index >= clusterIDs.length) return -1;
		return clusterIDs[index];
	}

	// Use first
	private void initBlockInfo(){
		this.blocks = new MemoryBlock[blockNum];
		this.allocated = 0;

		this.firstPoint = new LookupPointer[replicate * MEM_TYPES];
		this.lastPoint = new LookupPointer[replicate * MEM_TYPES];

		for(int i = 0; i < this.replicate * MEM_TYPES; i++){
			this.firstPoint[i] = LookupPointer.valueOf(-1, -1);
			this.lastPoint[i] = LookupPointer.valueOf(-1, -1);
		}

		this.contentFirstPoint = new LookupPointer[replicate * contentNum];
		this.contentLastPoint = new LookupPointer[replicate * contentNum];
		this.contentSkipFirstPoint = new LookupPointer[replicate * contentNum];
		this.contentSkipLastPoint = new LookupPointer[replicate * contentNum];
		this.appendixFirstPoint = new LookupPointer[replicate * appendixNum];
		this.appendixLastPoint = new LookupPointer[replicate * appendixNum];

		for(int i = 0; i < replicate * contentNum; i++){
			this.contentFirstPoint[i] = LookupPointer.valueOf(-1, -1);
			this.contentLastPoint[i] = LookupPointer.valueOf(-1, -1);
			this.contentSkipFirstPoint[i] = LookupPointer.valueOf(-1, -1);
			this.contentSkipLastPoint[i] = LookupPointer.valueOf(-1, -1);
		}

		for(int i = 0; i < this.replicate * appendixNum; i++){
			this.appendixFirstPoint[i] = LookupPointer.valueOf(-1, -1);
			this.appendixLastPoint[i] = LookupPointer.valueOf(-1, -1);
		}
	}

	public LookupPointer getFirstPointer(int cluster, int type, int subtype){
		int clusterSet = clusterIndex(cluster);
		switch(type){
		case MEM_DICT:
		case MEM_INVINDEX:
		case MEM_SKIPPTR:
		case MEM_KEYVAL:
			if(clusterSet * MEM_TYPES + type >= replicate * MEM_TYPES) return LookupPointer.EMPTY;
			return firstPoint[clusterSet * MEM_TYPES + type].withCluster(cluster);
		case MEM_CONT:
			return contentFirstPoint[clusterSet * contentNum + subtype].withCluster(cluster);
		case MEM_SKIPCONT:
			return contentSkipFirstPoint[clusterSet * contentNum + subtype].withCluster(cluster);
		case MEM_APX:
			return appendixFirstPoint[clusterSet * appendixNum + subtype].withCluster(cluster);
		default:
			return LookupPointer.EMPTY;
		}
	}

	public LookupPointer getLastPointer(int cluster, int type, int subtype){
		int clusterSet = clusterIndex(cluster);
		switch(type){
		case MEM_DICT:
		case MEM_INVINDEX:
		case MEM_SKIPPTR:
		case MEM_KEYVAL:
			if(cluster * MEM_TYPES + type >= replicate * MEM_TYPES) return LookupPointer.EMPTY;
			return lastPoint[clusterSet * MEM_TYPES + type].withCluster(cluster);
		case MEM_CONT:
			return contentLastPoint[clusterSet * contentNum + subtype].withCluster(cluster);
		case MEM_SKIPCONT:
			return contentSkipLastPoint[clusterSet * contentNum + subtype].withCluster(cluster);
		case MEM_APX:
			return appendixLastPoint[clusterSet * appendixNum + subtype].withCluster(cluster);
		default:
			return LookupPointer.EMPTY;
		}
	}

	public void setFirstPointer(int cluster, int type, int subtype, int block, int point){
		int clusterSet = clusterIndex(cluster);
		switch(type){
		case MEM_DICT:
		case MEM_INVINDEX:
		case MEM_SKIPPTR:
		case MEM_KEYVAL:
			if(clusterSet * MEM_TYPES + type >= replicate * MEM_TYPES) return;
			firstPoint[clusterSet * MEM_TYPES + type].modify(block, point);
			break;
		case MEM_CONT:
			contentFirstPoint[clusterSet * contentNum + subtype].modify(block, point);
			break;
		case MEM_SKIPCONT:
			contentSkipFirstPoint[clusterSet * contentNum + subtype].modify(block, point);
			break;
		case MEM_APX:
			appendixFirstPoint[clusterSet * appendixNum + subtype].modify(block, point);
			break;
		default:
			throw new IllegalArgumentException("Unknown block type: " + type);
		}
	}

	public void setLastPointer(int cluster, int type, int subtype, int block, int point){
		int clusterSet = clusterIndex(cluster);
		switch(type){
		case MEM_DICT:
		case MEM_INVINDEX:
		case MEM_SKIPPTR:
		case MEM_KEYVAL:
			if(clusterSet * MEM_TYPES + type >= replicate * MEM_TYPES) return;
			lastPoint[clusterSet * MEM_TYPES + type].modify(block, point);
			break;
		case MEM_CONT:
			contentLastPoint[clusterSet * contentNum + subtype].modify(block, point);
			break;
		case MEM_SKIPCONT:
			contentSkipLastPoint[clusterSet * contentNum + subtype].modify(block, point);
			break;
		case MEM_APX:
			appendixLastPoint[clusterSet * appendixNum + subtype].modify(block, point);
			break;
		}
	}

	public void initializeAllocate(int clusterID){
		if(clusterID < 0) return;
		int i;
		allocateBlock(MEM_DICT, 0, clusterID);
		allocateBlock(MEM_SKIPPTR, 0, clusterID);
		allocateBlock(MEM_INVINDEX, 0, clusterID);
		for(i = 0; i < contentNum; i++)
			allocateBlock(MEM_SKIPCONT, i, clusterID);
		for(i = 0; i < contentNum; i++)
			allocateBlock(MEM_CONT, i, clusterID);
		for(i = 0; i < appendixNum; i++)
			allocateBlock(MEM_APX, i, clusterID);
	}

	public MemoryBlock allocateBlock(int type, int subtype, int clusterID, int newID){
		int clusterSet = clusterIndex(clusterID);
		assignedIDs[clusterSet] = newID;
		return allocateBlock(type, subtype, clusterID);
	}

	public MemoryBlock allocateBlock(int type, int subtype, int clusterID){
		int clusterSet = clusterIndex(clusterID);
		if(clusterID < 0 || clusterSet < 0) return null;
		int vacantIndex = vacantBlock();

		if(blocks[vacantIndex] == null){
			// Get byte array space
			ByteBuffer newBuffer = disks.getBuffer(clusterID, assignedIDs[clusterSet]);

			switch(type){
			case MEM_DICT:
				blocks[vacantIndex] =
					new DictionaryBlock(newBuffer, blockSize, assignedIDs[clusterSet], clusterID);

				blocks[vacantIndex].setOption(DictionaryBlock.OPT_TYPES, contentNum);
				break;

			case MEM_KEYVAL:
				blocks[vacantIndex] =
					new KeyValueBlock(newBuffer, blockSize, assignedIDs[clusterSet], clusterID);
				break;

			case MEM_SKIPPTR:
				blocks[vacantIndex] =
					new SkipPointerBlock(newBuffer, blockSize, assignedIDs[clusterSet], clusterID);
				if(!getLastPointer(clusterID, type, 0).equals(LookupPointer.EMPTY))
					blocks[getLastPointer(clusterID, type, 0).block()].setOption(1, assignedIDs[clusterSet]);
				blocks[vacantIndex].setOption(SkipPointerBlock.OPT_NBLOCK, -1);
				blocks[vacantIndex].setOption(SkipPointerBlock.OPT_TYPE, MEM_SKIPPTR);
				break;

			case MEM_INVINDEX:
				blocks[vacantIndex] =
					new InvertIndexBlock(newBuffer, blockSize, assignedIDs[clusterSet], clusterID);
				blocks[vacantIndex].setOption(InvertIndexBlock.OPT_TERMNUM, Constants.PERUSER);
				blocks[vacantIndex].setOption(InvertIndexBlock.OPT_BOOLFLAG, boolFlagNum);
				blocks[vacantIndex].setOption(InvertIndexBlock.OPT_INTFLAG, intFlagNum);
				blocks[vacantIndex].setOption(InvertIndexBlock.OPT_LONGFLAG, longFlagNum);
				blocks[vacantIndex].setOption(InvertIndexBlock.OPT_FLOATFLAG, floatFlagNum);
				blocks[vacantIndex].setOption(InvertIndexBlock.OPT_UNIQUE, uniqueKeyIndex);
				break;

			case MEM_SKIPCONT:
				blocks[vacantIndex] =
					new ContentSkipPointerBlock(newBuffer, blockSize, assignedIDs[clusterSet], clusterID);
				if(!getLastPointer(clusterID, type, subtype).equals(LookupPointer.EMPTY))
					blocks[getLastPointer(clusterID, type, subtype).block()].setOption(
						ContentSkipPointerBlock.OPT_NBLOCK, assignedIDs[clusterSet]);

				blocks[vacantIndex].setOption(ContentSkipPointerBlock.OPT_NBLOCK, -1);
				blocks[vacantIndex].setOption(ContentSkipPointerBlock.OPT_PBLOCK,
					getLastPointer(clusterID, type, subtype).block());
				blocks[vacantIndex].setOption(ContentSkipPointerBlock.OPT_SUBTYPE, subtype);
				break;

			case MEM_CONT:
				blocks[vacantIndex] =
					new ContentBlock(newBuffer, blockSize, assignedIDs[clusterSet], clusterID);

				if(!getLastPointer(clusterID, type, subtype).equals(LookupPointer.EMPTY))
					blocks[getLastPointer(clusterID, type, subtype).block()].setOption(ContentBlock.OPT_NEXT,
						assignedIDs[clusterSet]);
				blocks[vacantIndex].setOption(ContentBlock.OPT_APP, appendixNum);
				blocks[vacantIndex].setOption(ContentBlock.OPT_NEXT, -1);
				blocks[vacantIndex].setOption(ContentBlock.OPT_TYPE, subtype);

				break;

			case MEM_APX:
				blocks[vacantIndex] =
					new AppendixBlock(newBuffer, blockSize, assignedIDs[clusterSet], clusterID);

				blocks[vacantIndex].setOption(AppendixBlock.OPT_TYPE, subtype);
				break;

			default:
				// Not a type
				throw new IndexOutOfBoundsException();
			}

			// Set target pointer
			setTargetPointer(clusterID, type, subtype, assignedIDs[clusterSet]);

			assignedIDs[clusterSet]++;

			// Put block information to disk
			blocks[vacantIndex].setHeader();

			return blocks[vacantIndex];
		}

		return null;
	}

	private void setTargetPointer(int clusterID, int type, int subtype, int newBlockID){
		if(getFirstPointer(clusterID, type, subtype).equals(LookupPointer.EMPTY))
			setFirstPointer(clusterID, type, subtype, newBlockID, 0);
		if(getLastPointer(clusterID, type, subtype).equals(LookupPointer.EMPTY))
			setLastPointer(clusterID, type, subtype, newBlockID, 0);
	}

	private MemoryBlock allocateByBuffer(ByteBuffer buffer){
		MemoryBlock tempBlock = new MemoryBlock(buffer){
			@Override
			public void setOption(int option, int value){}

			@Override
			public int getOptionSize(){
				return 0;
			}

			@Override
			public byte[] getOptionByteStream(){
				return null;
			}

			@Override
			public int getOption(int num){
				return 0;
			}

			@Override
			public String getClassName(){
				return null;
			}
		};

		int type = tempBlock.getType();
		int cluster = tempBlock.getCluster();
		int clusterSet = clusterIndex(cluster);
		if(clusterSet == -1){
			for(int i = 0; i < clusterIDs.length; i++){
				if(clusterIDs[i] == -1){
					clusterIDs[i] = cluster;
					clusterSet = i;
					break;
				}
			}
		}
		int ID = tempBlock.getID();
		int subtype = 0;

		int vacantIndex = vacantBlock();

		switch(type){
		case MEM_DICT:
			blocks[vacantIndex] = new DictionaryBlock(buffer);
			break;
		case MEM_KEYVAL:
			blocks[vacantIndex] = new KeyValueBlock(buffer);
			break;
		case MEM_SKIPPTR:
			blocks[vacantIndex] = new SkipPointerBlock(buffer);
			type = blocks[vacantIndex].getOption(SkipPointerBlock.OPT_TYPE);
			subtype = blocks[vacantIndex].getOption(SkipPointerBlock.OPT_SUBTYPE);
			break;
		case MEM_SKIPCONT:
			blocks[vacantIndex] = new ContentSkipPointerBlock(buffer);
			subtype = blocks[vacantIndex].getOption(ContentSkipPointerBlock.OPT_SUBTYPE);
			break;
		case MEM_INVINDEX:
			blocks[vacantIndex] = new InvertIndexBlock(buffer);
			break;
		case MEM_CONT:
			blocks[vacantIndex] = new ContentBlock(buffer);
			subtype = blocks[vacantIndex].getOption(ContentBlock.OPT_TYPE);
			break;
		case MEM_APX:
			blocks[vacantIndex] = new AppendixBlock(buffer);
			subtype = blocks[vacantIndex].getOption(AppendixBlock.OPT_TYPE);
			break;
		default:
			// Not a type
			throw new IndexOutOfBoundsException("The type doesn't exist.");
		}
		allocated++;

		// Set target pointer
		// System.out.printf("[DEBUG]Cluster: %d, Type: %d, Subtype: %d%n", cluster, type, subtype);
		setTargetPointer(cluster, type, subtype, ID);

		if(assignedIDs[clusterSet] < ID) assignedIDs[clusterSet] = ID + 1;

		return blocks[vacantIndex];
	}

	public MemoryBlock dispense(int cluster, int block){
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] == null) continue;
			if(blocks[i].getID() == block && blocks[i].getCluster() == cluster) return blocks[i];
		}

		return null;
	}

	public MemoryBlock dispense(LookupPointer ptr){
		return dispense(ptr.cluster(), ptr.block());
	}

	private boolean save(int memIndex){
		return save(memIndex, true, "./backup");
	}

	public boolean save(int memIndex, boolean isCopy, String outputDir){
		MemoryBlock memBlock = blocks[memIndex];
		if(memBlock == null) return false;
		memBlock.setHeader();

		// Copy file
		if(!isCopy) return true;
		File outputFile =
			new File(String.format("/%s/%d_%d.backup", (outputDir == null? ".": outputDir),
				memBlock.getCluster(), memBlock.getID()));
		if(!outputFile.exists()){
			try{
				outputFile.createNewFile();
			}catch(IOException e){
				return false;
			}
		}
		if(!outputFile.canWrite()){
			LogParser.severe("Directory is write-protected, backup process canceled.");
			return false;
		}
		FileChannel fromChannel = disks.getChannel(memBlock.getCluster(), memBlock.getID());
		try{
			FileChannel toChannel = new FileOutputStream(outputFile).getChannel();
			toChannel.transferFrom(fromChannel, 0, fromChannel.size());
		}catch(FileNotFoundException e){
			e.printStackTrace();
		}catch(IOException e){
			e.printStackTrace();
		}

		return true;
	}

	public void readAll(){
		Set<Entry<ClusterTuple, FileBufferTuple>> entrySet = disks.getFileMap().entrySet();
		if(entrySet.size() > 0) System.out.println(entrySet.size() + " file exists in " + disks.getDir());
		for(Entry<ClusterTuple, FileBufferTuple> eachFile: entrySet){
			allocateByBuffer(eachFile.getValue().buffer());
		}
	}

	public MemoryBlock block(int index){
		if(index >= blocks.length) return null;
		return blocks[index];
	}

	public int blockLength(){
		return blocks.length;
	}

	public int memoryAmount(int type){
		int amount = 0;

		for(int i = 0; i < blocks.length; i++)
			if(blocks[i] != null && blocks[i].getType() == type) amount++;

		return amount;
	}

	public int checkFreeSpace(){
		File root = new File("/");
		int availableBlock = (int)(root.getFreeSpace() / this.blockSize);
		if(availableBlock == 0)
			System.out.println("No block can save to disk. " + "Please optimize or drop some stuffs.");

		return availableBlock;
	}

	public ArrayList<MemoryInfoMaterial> getMemStatus(){
		ArrayList<MemoryInfoMaterial> infoList = new ArrayList<MemoryInfoMaterial>();

		for(int j = 0; j < replicate; j++){
			for(int i = 0; i < blocks.length; i++){
				if(blocks[i] != null && blocks[i].getCluster() == clusterIDs[j])
					infoList.add(new MemoryInfoMaterial(blocks[i].getID(), blocks[i].getUsed(), blockSize,
						blocks[i].getCluster(), blocks[i].getClassName()));
			}
		}

		return infoList;
	}

	public void showMemoryElapsed(){
		System.out.println("Maximum: " + blockNum);
		for(int j = 0; j < replicate; j++){
			if(clusterIDs[j] != -1) System.out.printf("--- Cluster %3d ---%n", clusterIDs[j]);
			for(int i = 0; i < blocks.length; i++){
				if(blocks[i] != null && blocks[i].getCluster() == clusterIDs[j])
					System.out.printf("%d: %7d (%3.2f %%) %s%n", blocks[i].getID(), blocks[i].getUsed(),
						(blocks[i].getUsed() * 100.0 / blockSize), blocks[i].getClassName());
			}
		}
	}

	public void saveAllMemory(){
		// Save memory block to disk
		System.out.print("Writing to memory... ");
		for(int i = 0; i < blocks.length; i++)
			save(i);
		System.out.println("Done.");
	}

	public int getContentCount() throws Exception{
		int result = 0;
		// Count from memory
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null && blocks[i] instanceof ContentBlock)
				result += ((ContentBlock)blocks[i]).getCount();
		}

		return result;
	}

	public int getTermCount() throws Exception{
		int result = 0;
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null && blocks[i] instanceof DictionaryBlock){
				int count = ((DictionaryBlock)blocks[i]).getTermCount();
				result += count;
			}
		}

		return result;
	}

	// public void doRevertFromFile(long ID) throws Exception{
	// String directory = "./backup_" + Long.toBinaryString(ID);
	// File dirdesc = new File(directory);
	// if(!dirdesc.exists()) return;
	//
	// // Drop all blocks
	// for(int i = 0; i < blocks.length; i++)
	// blocks[i] = null;
	//
	// disks.dropAll();
	// for(int i = 0; i < assignedIDs.length; i++)
	// assignedIDs[i] = 0;
	// }

	public boolean isOnMemory(int index, int cluster){
		if(dispense(cluster, index) != null) return false;
		else return true;
	}

	public int getClusterBlockAmount(int clusterID){
		int clusterIndex = clusterIndex(clusterID);
		if(clusterIndex == -1) return -1;
		return assignedIDs[clusterIndex];
	}

	public void setClusterPointInfo(ArrayType info){
		MessagePackObject[] pointerArray = info.asArray();
		if(pointerArray.length < 2 * (MEM_TYPES + (contentNum * 2) + appendixNum) + 1)
			throw new IllegalArgumentException("This message's arguments are illegal");
		int counter = 0;

		int clusterID = pointerArray[counter++].asInt();
		int clusterIndex = clusterIndex(clusterID);
		if(clusterIndex == -1)
			throw new IllegalArgumentException("Cluster index " + clusterID + " doesn't exist.");
		LookupPointer tempPoint;
		for(int i = 0; i < MEM_TYPES; i++){
			switch(i){
			case MEM_DICT:
			case MEM_INVINDEX:
			case MEM_SKIPPTR:
				tempPoint = LookupPointer.fromBytes(pointerArray[counter++].asByteArray());
				setFirstPointer(clusterID, i, 0, tempPoint.block(), tempPoint.point());
				tempPoint = LookupPointer.fromBytes(pointerArray[counter++].asByteArray());
				setLastPointer(clusterID, i, 0, tempPoint.block(), tempPoint.point());
				break;
			case MEM_SKIPCONT:
			case MEM_CONT:
				for(int j = 0; j < contentNum; j++){
					tempPoint = LookupPointer.fromBytes(pointerArray[counter++].asByteArray());
					setFirstPointer(clusterID, i, j, tempPoint.block(), tempPoint.point());
					tempPoint = LookupPointer.fromBytes(pointerArray[counter++].asByteArray());
					setLastPointer(clusterID, i, j, tempPoint.block(), tempPoint.point());
				}
				break;
			case MEM_APX:
				for(int j = 0; j < appendixNum; j++){
					tempPoint = LookupPointer.fromBytes(pointerArray[counter++].asByteArray());
					setFirstPointer(clusterID, i, j, tempPoint.block(), tempPoint.point());
					tempPoint = LookupPointer.fromBytes(pointerArray[counter++].asByteArray());
					setLastPointer(clusterID, i, j, tempPoint.block(), tempPoint.point());
				}
				break;
			default:
				break;
			}
		}
	}

	public ArrayType getClusterPointInfo(int clusterID){
		int clusterIndex = clusterIndex(clusterID);
		if(clusterIndex == -1) return null;

		int arrayLen = 2 * (MEM_TYPES + (contentNum * 2) + appendixNum) + 1;
		int counter = 0;
		MessagePackObject[] pointerArray = new MessagePackObject[arrayLen];

		pointerArray[counter++] = IntegerType.create(clusterID);
		for(int i = 0; i < MEM_TYPES; i++){
			switch(i){
			case MEM_DICT:
			case MEM_INVINDEX:
			case MEM_SKIPPTR:
				pointerArray[counter++] = RawType.create(getFirstPointer(clusterID, i, 0).toBytes());
				pointerArray[counter++] = RawType.create(getLastPointer(clusterID, i, 0).toBytes());
				break;
			case MEM_SKIPCONT:
			case MEM_CONT:
				for(int j = 0; j < contentNum; j++){
					pointerArray[counter++] = RawType.create(getFirstPointer(clusterID, i, j).toBytes());
					pointerArray[counter++] = RawType.create(getLastPointer(clusterID, i, j).toBytes());
				}
				break;
			case MEM_APX:
				for(int j = 0; j < appendixNum; j++){
					pointerArray[counter++] = RawType.create(getFirstPointer(clusterID, i, j).toBytes());
					pointerArray[counter++] = RawType.create(getLastPointer(clusterID, i, j).toBytes());
				}
				break;
			default:
				break;
			}
		}
		for(; counter < pointerArray.length; counter++)
			pointerArray[counter++] = NilType.create();

		return ArrayType.create(pointerArray);
	}

	// obtain higher block which has same cluster
	public MemoryBlock getNextBlock(MemoryBlock prevBlock){
		int cluster = prevBlock.getCluster();
		int clusterSet = clusterIndex(cluster);
		int ID = prevBlock.getID();
		int type = prevBlock.getType();

		for(int i = ID; i < assignedIDs[clusterSet]; i++){
			MemoryBlock newBlock = dispense(cluster, i);
			if(newBlock.getType() == type) return newBlock;
		}

		return null;
	}

	public void overwriteData(int cluster, int block, byte[] data){
		disks.overwriteData(cluster, block, data);
	}

	// Returns compressed data specified by cluster and block number
	public byte[] callForData(int cluster, int block){
		ByteBuffer buf = disks.getBuffer(cluster, block);
		if(buf == null) return null;
		return buf.array();
	}
}
